package com.gitee.cnlongdb.basic.utils;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.channels.FileLock;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitee.cnlongdb.basic.config.LongDbConfig;
import com.gitee.cnlongdb.basic.constant.LongDbConstant;

/**
 * 系统名称：LongFileOperator.java
 * 模块名称：
 * 模块描述：文件读写工具类
 * 功能列表：
 * 模块作者：ZOUYONG
 * 开发时间：2018年12月5日 下午9:43:21
 * 模块路径：com.gitee.cnlongdb.basic.utils.LongFileOperator
 * 更新记录：
 */
public class LongFileOperator {

	private static final Logger logger = (Logger) LoggerFactory.getLogger(LongFileOperator.class);

	/**
	 * 功能描述：快速读取文件内容-方式一
	 * 模块作者：ZOUYONG
	 * 开发时间：2018年11月15日 下午11:02:43
	 * 更新记录：
	 * 返回数据：String
	 */
	public static String readFileContentByByteBuffer(File tabFile) {
		long time1 = System.currentTimeMillis();
		StringBuffer jsonStr = new StringBuffer();
		FileInputStream in = null;
		try {
			// 获取源文件和目标文件的输入输出流
			in = new FileInputStream(tabFile);
			// 获取输入输出通道
			FileChannel fcIn = in.getChannel();
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			while (true) {
				// clear方法重设缓冲区，使它可以接受读入的数据
				buffer.clear();
				// 从输入通道中将数据读到缓冲区
				int r = fcIn.read(buffer);
				if (r == -1) {
					break;
				}
				// flip方法让缓冲区可以将新读入的数据写入另一个通道
				buffer.flip();
				jsonStr.append(LongDbConstant.charset.decode(buffer));
			}
		} catch (Exception e) {
			throw new RuntimeException("数据库查询异常："+e.getMessage(), e);
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					throw new RuntimeException("关闭数据读取通道异常："+e.getMessage(), e);
				}
			}
		}
		logger.debug("[getFileContent]获取表中数据文件的内容共耗时：" + Float.toString((System.currentTimeMillis() - time1) / 1000F) + " 秒.");
		return jsonStr.toString();
	}

	/**
	 * 功能描述：快速读取文件内容-方式二
	 × 模块作者：ZOUYONG
	 × 开发时间：2018年11月23日 下午12:27:44
	 × 返回数据：String
	 */
	public static String readFileContentByMappedBuffer(File tabFile) {
		
		long time1 = System.currentTimeMillis();
		String result = null;
		// 定义读写流，通道
		FileInputStream in = null;
		FileChannel fin = null;
		MappedByteBuffer map = null; // 声明内存映射
		
		try {
			try {
				in = new FileInputStream(tabFile);
				fin = in.getChannel();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
				throw new RuntimeException("获取数据文件流异常："+e.getMessage(), e);
			}
			
			try {
				// 通过通道获取内存映射
				map = fin.map(MapMode.READ_ONLY, 0, tabFile.length());
			} catch (IOException e) {
				e.printStackTrace();
				throw new RuntimeException("声明内存映射异常："+e.getMessage(), e);
			}

			// 将存映射放入到变量中进行输出
			byte[] data = new byte[(int) tabFile.length()];
			int foot = 0; // 文件大小
			while (map.hasRemaining()) {
				data[foot++] = map.get();
			}
			try {
				result = new String(data, LongDbConstant.longdbSystemCharset);
			} catch (Exception e) {
				throw new RuntimeException("转换表数据异常"+e.getMessage(), e);
			}
		} catch(Exception e) {
			throw e;
		} finally {
			try {
				if (in != null) {
					in.close();
				}
				if(null != fin) {
					fin.close();
				}
				if(null != map) {
					clean(map);
				}
			} catch (Exception e) {
				logger.error("关闭数据读取通道异常："+e.getMessage());
			}
		}
		logger.debug("[readFileContentByMappedBuffer]["+tabFile.getName()+"]获取表中数据文件的内容共耗时：" + Float.toString((System.currentTimeMillis() - time1) / 1000F) + " 秒.");
		return result;
	}

	/**
	 * 其实讲到这里该问题的解决办法已然清晰明了了——就是在删除索引文件的同时还取消对应的内存映射，删除mapped对象。
	 * 不过令人遗憾的是，Java并没有特别好的解决方案——令人有些惊讶的是，Java没有为MappedByteBuffer提供unmap的方法，
	 * 该方法甚至要等到Java 10才会被引入 ,DirectByteBufferR类是不是一个公有类 class DirectByteBufferR
	 * extends DirectByteBuffer implements DirectBuffer 使用默认访问修饰符
	 * 不过Java倒是提供了内部的“临时”解决方案——DirectByteBufferR.cleaner().clean() 切记这只是临时方法，
	 * 毕竟该类在Java9中就正式被隐藏了，而且也不是所有JVM厂商都有这个类。
	 * 还有一个解决办法就是显式调用System.gc()，让gc赶在cache失效前就进行回收。
	 * 不过坦率地说，这个方法弊端更多：首先显式调用GC是强烈不被推荐使用的，
	 * 其次很多生产环境甚至禁用了显式GC调用，所以这个办法最终没有被当做这个bug的解决方案。
	 */
	public static void clean(final MappedByteBuffer buffer) throws Exception {
		if (buffer == null) {
			return;
		}
		buffer.force();
		AccessController.doPrivileged(new PrivilegedAction<Object>() {// Privileged特权
			@Override
			public Object run() {
				try {
					Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]);
					getCleanerMethod.setAccessible(true);
					sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(buffer, new Object[0]);
					if(null != cleaner) {
						cleaner.clean();
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
				return null;
			}
		});
		/**
		 * 在MyEclipse中编写Java代码时，用到了Cleaner，import sun.misc.Cleaner;可是Eclipse提示：
		 * Access restriction: The type Cleaner is not accessible due to
		 * restriction on required library *\rt.jar Access restriction : The
		 * constructor Cleaner() is not accessible due to restriction on
		 * required library *\rt.jar
		 * 解决方案1（推荐）： 只需要在project build path中先移除JRE System Library，再添加库JRE
		 * System Library，重新编译后就一切正常了。 解决方案2： Windows -> Preferences -> Java ->
		 * Compiler -> Errors/Warnings -> Deprecated and trstricted API ->
		 * Forbidden reference (access rules): -> change to warning
		 */
	}
	
	/**
	 * 功能描述：快速读取文件内容-方式二
	 * 模块作者：ZOUYONG
	 * 开发时间：2018年11月24日 上午7:46:47
	 * 更新记录：
	 * 返回数据：String
	 */
	public static String readFileContentByRandomAccess(File dataFile) {
		long time1 = System.currentTimeMillis();
		String result = null;
		RandomAccessFile fout = null;
		FileChannel fcout = null;
		try {
			fout = new RandomAccessFile(dataFile, "r");
			long filelength = fout.length(); // 获取文件的长度
			fout.seek(filelength); // 将文件的读写指针定位到文件的末尾
			fcout = fout.getChannel(); // 打开文件通道
			fout.seek(0); // 将指针移到文件的开头
			// 一次性读取，就是把文件中的内容都读取到字节数组中
			byte[] buffer = new byte[(int) filelength];
			fout.read(buffer);
			result = new String(buffer, LongDbConstant.longdbSystemCharset);
		} catch (Exception e) {
			throw new RuntimeException("将数据写入表操作异常：" + e.getMessage(), e);
		} finally {
			if (fcout != null) {
				try {
					fcout.close();
				} catch (IOException e) {
					fcout = null;
				}
			}
			if (fout != null) {
				try {
					fout.close();
				} catch (IOException e) {
					fout = null;
				}
			}
		}
		logger.debug("[readFileByContent]获取表中数据文件的内容共耗时：" + Float.toString((System.currentTimeMillis() - time1) / 1000F) + " 秒.");
		return result;
	}
	
	/**
	 * 功能描述：一行一行读取文件
	 × 模块作者：ZOUYONG
	 × 开发时间：2018年11月28日 下午5:56:09
	 × 返回数据：String
	 */
	public static String readFileContentByLine(File dataFile) {
		// BufferedReader:从字符输入流中读取文本，缓冲各个字符，从而实现字符、数组和行的高效读取。
		BufferedReader bufReader = null;
		BufferedInputStream bis = null;
		StringBuffer fileText = new StringBuffer();
		try {
			// FileReader:用来读取字符文件的便捷类。
			bis = new BufferedInputStream(new FileInputStream(dataFile));
			bufReader = new BufferedReader(
				new InputStreamReader(bis, LongDbConstant.longdbSystemCharset), 
				LongDbConfig.longdbTableFileBufferedReaderSize
			);
			String content = null;
			while ((content = bufReader.readLine()) != null) {
				fileText.append(content);
			}
		} catch (Exception e) {
			e.getStackTrace();
		} finally {
			if (bufReader != null) {
				try {
					bufReader.close();
				} catch (IOException e) {
					bufReader = null;
				}
			}
			if(null != bis) {
				try {
					bis.close();
				} catch (IOException e) {
					bis = null;
				}
			}
		}
		return fileText.toString();
	}

	/**
	 * 功能描述：获取文件第一行数据
	 * 模块作者：ZOUYONG
	 * 开发时间：2018年11月24日 下午7:51:21
	 * 更新记录：
	 * 返回数据：String
	 */
	public static String readFileOneLine(File file) {
		// BufferedReader:从字符输入流中读取文本，缓冲各个字符，从而实现字符、数组和行的高效读取。
		BufferedReader bufReader = null;
		BufferedInputStream bis = null;
		try {
			// FileReader:用来读取字符文件的便捷类。
			bis = new BufferedInputStream(new FileInputStream(file));
			bufReader = new BufferedReader(
				new InputStreamReader(bis, LongDbConstant.longdbSystemCharset), 
				LongDbConfig.longdbTableFileBufferedReaderSize
			);
			return bufReader.readLine();
		} catch (Exception e) {
			e.getStackTrace();
		} finally {
			if (null != bufReader) {
				try {
					bufReader.close();
				} catch (IOException e) {
					bufReader = null;
				}
			}
			if(null != bis) {
				try {
					bis.close();
				} catch (IOException e) {
					bis = null;
				}
			}
		}
		return null;
	}
	
	/**
	 * 功能描述：获取文件中指定某行的数据
	 * 模块作者：ZOUYONG
	 * 开发时间：2018年11月25日 上午11:53:47
	 * 更新记录：
	 * 返回数据：String
	 */
	public static String readFileSomeLineData(File file, int rowNumber) {
		// BufferedReader:从字符输入流中读取文本，缓冲各个字符，从而实现字符、数组和行的高效读取。
		BufferedReader bufReader = null;
		BufferedInputStream bis = null;
		String content = null;
		try {
			// FileReader:用来读取字符文件的便捷类。
			bis = new BufferedInputStream(new FileInputStream(file));
			bufReader = new BufferedReader(
				new InputStreamReader(bis, LongDbConstant.longdbSystemCharset), 
				LongDbConfig.longdbTableFileBufferedReaderSize
			);
			int number = 1;
			while ((content = bufReader.readLine()) != null) {
				if(number == rowNumber) {
					break;
				}
				number++;
			}
		} catch (Exception e) {
			e.getStackTrace();
		} finally {
			if (bufReader != null) {
				try {
					bufReader.close();
				} catch (IOException e) {
					bufReader = null;
				}
			}
			if(null != bis) {
				try {
					bis.close();
				} catch (IOException e) {
					bis = null;
				}
			}
		}
		return content;
	}
	
	/**
	 * 功能描述：获取文件中指定某行的数据
	 * 模块作者：ZOUYONG
	 * 开发时间：2018年11月25日 上午11:53:47
	 * 更新记录：
	 * 返回数据：String
	 */
	public static List<String> readFileToList(File file) {
		// BufferedReader:从字符输入流中读取文本，缓冲各个字符，从而实现字符、数组和行的高效读取。
		BufferedReader bufReader = null;
		BufferedInputStream bis = null;
		List<String> list = new ArrayList<String>();
		try {
			// FileReader:用来读取字符文件的便捷类。
			bis = new BufferedInputStream(new FileInputStream(file));
			bufReader = new BufferedReader(
				new InputStreamReader(bis, LongDbConstant.longdbSystemCharset), 
				LongDbConfig.longdbTableFileBufferedReaderSize
			);
			String content = null;
			while ((content = bufReader.readLine()) != null) {
				if(StringUtils.isNotBlank(content)) {
					list.add(content);
				}
			}
		} catch (Exception e) {
			e.getStackTrace();
		} finally {
			if (bufReader != null) {
				try {
					bufReader.close();
				} catch (IOException e) {
					bufReader = null;
				}
			}
			if(null != bis) {
				try {
					bis.close();
				} catch (IOException e) {
					bis = null;
				}
			}
		}
		return list;
	}

	/**
	 * 功能描述：新增的数据写入文件末尾
	 * 模块作者：ZOUYONG
	 * 开发时间：2018年11月24日 上午8:53:17
	 * 更新记录：
	 * 返回数据：void
	 */
	public static void writeAvailableTableFileContent(File availableTableFile, String content, boolean isEmpty) {
		
		RandomAccessFile fout = null;
		FileChannel fcout = null;
		try {
			fout = new RandomAccessFile(availableTableFile, "rw");
			fcout = fout.getChannel();// 打开文件通道
			FileLock flout = null;
			while(true) {
				try {
					flout = fcout.tryLock();// 不断的请求锁
					break;
				} catch(Exception e) {
					logger.debug("插入数据，给表上锁异常：" + e.getMessage(), e);
				}
			}
			if(isEmpty) {
				fout.seek(fout.length());
				fout.setLength(0);
			} else {
				fout.seek(fout.length()); // 将文件的读写指针定位到文件的末尾
			}
			// 将数据写入表中
			fout.write(content.getBytes());
			flout.release();
        } catch (Exception e) {
        	e.printStackTrace();
        	throw new RuntimeException("将数据写入表操作异常："+e.getMessage(), e);
        } finally {
			if(fcout != null) {
			    try {
			        fcout.close();
			    } catch (IOException e) {
			        fcout = null;
			    }
			}
			if(fout != null) {
			    try {
			        fout.close();
			    } catch (IOException e) {
			        fout = null;
			    }
			}
        }
	}
	
	/**
	 * 功能描述：通过FileWriter写入文件内容
	 × 模块作者：ZOUYONG
	 × 开发时间：2018年11月16日 下午6:09:09
	 × 返回数据：void
	 */
	public static void writeFileByFileWriter(File file, String content) {
		FileWriter fw = null;
		try {
			fw = new FileWriter(file);
			fw.write(content);
			fw.flush();
		} catch(Exception e) {
			throw new RuntimeException("写入数据文件异常：" + e.getMessage(), e);
		} finally {
			try {
				if(null != fw) {
					fw.close();
				}
			} catch(Exception e) {
				throw new RuntimeException("写入数据关闭读取文件流通道异常：" + e.getMessage(), e);
			}
		}
	}

	/**
	 * 功能描述：通过FileOutputStream将数据写入文件中
	 * 模块作者：ZOUYONG
	 * 开发时间：2018年11月9日 下午6:55:41
	 * 更新记录：
	 * 返回数据：void
	 */
	public static void writeFileByStream(File file, String content) {
		FileOutputStream out = null;
		try {
			out = new FileOutputStream(file);
			out.write(content.getBytes());
			out.flush();
		} catch(Exception e) {
			throw new RuntimeException("写入数据文件异常：" + e.getMessage(), e);
		} finally {
			try {
				if(null != out) {
					out.close();
				}
			} catch(Exception e) {
				throw new RuntimeException("写入数据关闭读取文件流通道异常：" + e.getMessage(), e);
			}
		}
	}

	/**
	 * 功能描述：统计文件行数
	 * 模块作者：ZOUYONG
	 * 开发时间：2018年11月24日 下午11:15:28
	 * 更新记录：
	 * 返回数据：int
	 */
	public static Integer calcFileNumber(File file) {
		if(null == file || !file.exists()) {
			return null;
		}
		LineNumberReader lineNumberReader = null;
		try {
			lineNumberReader = new LineNumberReader(new FileReader(file));
			long fileLength = file.length();
			lineNumberReader.skip(fileLength);
			return lineNumberReader.getLineNumber();
		} catch(IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if(null != lineNumberReader) {
					lineNumberReader.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	/**
	 * 功能描述：将文件大小转换成具体大小
	 × 模块作者：ZOUYONG
	 × 开发时间：2018年11月23日 下午4:23:10
	 × 返回数据：String
	 */
	public static String getFileSize(long size) {
		// 获取到的size为：2097152000 1073741824
		int GB = 1024 * 1024 * 1024;//定义GB的计算常量
		int MB = 1024 * 1024;//定义MB的计算常量
		int KB = 1024;//定义KB的计算常量
		DecimalFormat df = new DecimalFormat("0.00");//格式化小数
		String resultSize = "";
		if (size / GB >= 1) {
			// 如果当前Byte的值大于等于1GB
			resultSize = df.format(size / (float) GB) + "GB";
		} else if (size / MB >= 1) {
		    // 如果当前Byte的值大于等于1MB
			resultSize = df.format(size / (float) MB) + "MB";
		} else if (size / KB >= 1) {
			// 如果当前Byte的值大于等于1KB
			resultSize = df.format(size / (float) KB) + "KB";
		} else {
			resultSize = size + "B";
		}
		return resultSize;
	}
	
	/**
	 * 功能描述：获取文件的行数
	 × 模块作者：ZOUYONG
	 × 开发时间：2019年2月13日 下午3:59:02
	 × 返回数据：long
	 */
	public static long getFileLineNumber(File file) {
		if(null == file || !file.exists()) {
			return 0;
		}
		try {
			// 获取文件总大小
			long fileLength = file.length();
			LineNumberReader lineNumberReader = new LineNumberReader(new FileReader(file));
			lineNumberReader.skip(fileLength);
			return lineNumberReader.getLineNumber();
		} catch(Exception e) {
			logger.error("获取文件行数异常：{}", e.getMessage());
			throw new RuntimeException(e);
		}
	}
	
}
